/*
 * anima/x86_64/space.cc - Address space functions for x86-64 architecture.
 * Copyright (C) 2007  Mikhail Vorozhtsov
 * See the LICENSE section of the README file for details.
 */

/* $Id$ */

#include HEADER (anima,space.hh)
#include HEADER (anima,cpu.hh)
#include HEADER (anima,init.hh)

START_NAMESPACE2 (anima,x86_64)

void
space_t::allocator_t::install (::x86_64::pe_t pml4e)
{
  space->maps_pml4e = pml4e;

  if (init::done_p ())
    {
    }
  else
    ::anima::mm::install (space);
}

mm::tte_t *
space_t::allocator_t::map (u64_t paddr)
{
  return (mm::tte_t *) ::anima::cpu_t::map_temp (paddr);
}

void
space_t::allocator_t::unmap ()
{
  ::anima::cpu_t::unmap_temp ();
}

::anima::pdb::lvl1e_t *
space_t::allocator_t::allocate ()
{
  return space->allocate (::anima::pdb::entry_t::USAGE_SPACE);
}

void
space_t::allocator_t::deallocate (::anima::pdb::lvl1e_t *lvl1e)
{
  space->deallocate (lvl1e);
}

mm::tte_t *
space_t::mapper_t::map (u64_t addr)
{
  mm::tte_t *tte = space->maps_pdpm.map (addr);

  if (!tte)
    return NULL;

  tte->wlock ();

  if (tte->P)
    {
      tte->unwlock ();
      return NULL;
    }

  ::anima::pdb::lvl1e_t *lvl1e
    = space->allocate (::anima::pdb::entry_t::USAGE_SPACE);

  if (!lvl1e)
    {
      tte->unwlock ();
      space->maps_pdpm.unmap (addr);
      return NULL;
    }

  tte->clear ().set_base (lvl1e->entry.base).set_NX ().set_RW ().set_P ();

  tte->unwlock ();

  return tte;
}

void
space_t::mapper_t::unmap (mm::tte_t *tte)
{
  space->maps_pdpm.unmap (tte);
}

END_NAMESPACE2

START_NAMESPACE (anima)

bool
space_t::init_instance ()
{
  pdb::lvl1e_t *iopb0_lvl1e = allocate (pdb::entry_t::USAGE_SPACE);

  if (!iopb0_lvl1e)
    return false;

  pdb::lvl1e_t *iopb1_lvl1e = allocate (pdb::entry_t::USAGE_SPACE);

  if (!iopb1_lvl1e)
    return false;

  iopb0_pte.set_raw (0).set_base (iopb0_lvl1e->entry.base)
    .set_NX ().set_RW ().set_P ();
  iopb1_pte.set_raw (0).set_base (iopb1_lvl1e->entry.base)
    .set_NX ().set_RW ().set_P ();

  maps_allocator.init (this);
  mapper.init (this);

  if (!maps_pdpm.init (&maps_allocator))
    return false;

  first_pml4e = first_pdpm.init (&mapper);

  if (!first_pml4e.P)
    return false;

  second_pml4e = second_pdpm.init (&mapper);

  if (!second_pml4e.P)
    return false;

  first_pml4e.set_US ().set_RW ();
  second_pml4e.set_US ().set_RW ();

  return true;
}

void *
space_t::map (u64_t addr, mm::tte_t::flag_t flags)
{
  ::x86_64::vaddr_t vaddr = addr;
  mm::tte_t *pte;

  if (vaddr.PML4 == 2)
    pte = first_pdpm.map (addr);
  else if (vaddr.PML4 == 3)
    pte = second_pdpm.map (addr);
  else
    return NULL;

  flags = (mm::tte_t::flag_t) (flags | mm::tte_t::FLAG_USER);

  pte->wlock ();

  if (pte->P)
    {
      pte->set_flags (flags);
      pte->unwlock ();
      return (void *) addr;
    }

  pdb::lvl1e_t *lvl1e = allocate (pdb::entry_t::USAGE_USER);

  if (!lvl1e)
    {
      pte->unwlock ();

      if (vaddr.PML4 == 2)
        first_pdpm.unmap (addr);
      else
        first_pdpm.unmap (addr);

      return NULL;
    }

  pte->clear ().set_flags (flags).set_base (lvl1e->entry.base).set_P ();

  pte->unwlock ();

  return (void *) addr;
}

void *
space_t::map (u64_t paddr, u64_t addr, mm::tte_t::flag_t flags)
{
  ::x86_64::vaddr_t vaddr = addr;
  mm::tte_t *pte;

  if (vaddr.PML4 == 2)
    pte = first_pdpm.map (addr);
  else if (vaddr.PML4 == 3)
    pte = second_pdpm.map (addr);
  else
    return NULL;

  flags = (mm::tte_t::flag_t) (flags | mm::tte_t::FLAG_USER);

  pte->wlock ();

  if (pte->P)
    {
      if (pte->get_ptr () == paddr)
        {
          pte->set_flags (flags);
          pte->unwlock ();
          return (void *) addr;
        }
      else
        {
          pte->unwlock ();
          return NULL;
        }
    }

  if (!pdb::add (paddr, true))
    {
      pte->unwlock ();

      if (vaddr.PML4 == 2)
        first_pdpm.unmap (addr);
      else
        first_pdpm.unmap (addr);

      return NULL;
    }

  pte->clear ().set_flags (flags).set_ptr (paddr).set_P ();

  pte->unwlock ();

  return (void *) addr;
}

void
space_t::unmap (u64_t addr)
{
  ::x86_64::vaddr_t vaddr = addr;
  mm::tte_t tte;

  if (vaddr.PML4 == 2)
    tte = first_pdpm.unmap (addr);
  else if (vaddr.PML4 == 3)
    tte = second_pdpm.unmap (addr);
  else
    return;

  if (!tte.P)
    return;

  ::x86_64::inv_tlb_entry (addr);

  pdb::lvl1e_t *lvl1e = pdb::get (tte.get_ptr ());

  if (!lvl1e)
    return;

  deallocate (lvl1e);
}

END_NAMESPACE

